add live relation

FFIB 5 years ago
parent
commit
cecb9c9913

+ 9 - 0
api/urls.py

@@ -4,6 +4,7 @@ from django.conf.urls import url
4 4
 
5 5
 from api import admin_views, mini_views, oauth_views, pack_views
6 6
 from pay import views as pay_views
7
+from live import views as live_views
7 8
 
8 9
 
9 10
 urlpatterns = [
@@ -21,6 +22,14 @@ urlpatterns += [
21 22
     url(r'^pay/wx/notify_url$', pay_views.wx_notify_url_api, name='wx_notify_url_api'),  # 支付异步通知回调地址
22 23
 ]
23 24
 
25
+#直播相关
26
+urlpatterns += [
27
+    url(r'^live/goods/detail$', live_views.room_goods_detail, name='room_goods_detail'),
28
+    url(r'^live/order/create$', live_views.live_order_create, name='live_order_create'),
29
+    url(r'^live/order/cancel$', live_views.live_order_cancel, name='live_order_cancel'),
30
+    url(r'^live/pay/notify_url$', live_views.notify_url_api, name='notify_url_api'),
31
+]
32
+
24 33
 urlpatterns += [
25 34
     url(r'^admin/login$', admin_views.login, name='login'),
26 35
 

+ 1 - 0
kodosale/settings.py

@@ -61,6 +61,7 @@ INSTALLED_APPS = [
61 61
     'kol',
62 62
     'pay',
63 63
     'simditor',
64
+    'live',
64 65
 ]
65 66
 
66 67
 MIDDLEWARE = [

+ 0 - 0
live/__init__.py


+ 30 - 0
live/admin.py

@@ -0,0 +1,30 @@
1
+from django.contrib import admin
2
+
3
+from live.models import RoomGoodsInfo, RoomInfo, AnchorInfo, liveGoodsInfo, RoomOrderInfo
4
+
5
+
6
+class AnchorInfoAdmin(admin.ModelAdmin):
7
+    list_display = ('anchor_id', 'unionid', 'anchor_name', 'anchor_avatar', 'openid', 'status', 'created_at', 'updated_at')
8
+
9
+
10
+class RoomInfoAdmin(admin.ModelAdmin):
11
+    list_display = ('room_id', 'name', 'live_status', 'start_time', 'end_time', 'cover_img', 'share_img', 'anchor_id', 'anchor_name', 'goods', 'status', 'created_at', 'updated_at')
12
+
13
+
14
+class liveGoodsInfoAdmin(admin.ModelAdmin):
15
+    list_display = ('goods_id', 'wx_goods_id', 'audit_id', 'audit_status', 'goods_img', 'coverImgUrl', 'name', 'price_type', 'price', 'price2', 'url', 'status', 'created_at', 'updated_at')
16
+
17
+
18
+class RoomGoodsInfoAdmin(admin.ModelAdmin):
19
+    list_display = ('room_id', 'anchor_id', 'goods_id', 'inventory', 'sale_infos', 'status', 'created_at', 'updated_at')
20
+
21
+class RoomOrderInfoAdmin(admin.ModelAdmin):
22
+    list_display = ('order_id', 'anchor_id', 'room_id', 'goods_id', 'share_openid', 'user_id', 'total_fee', 'name', 'phone', 'address', 'tracking_number', 'pay_status', 'status', 'created_at', 'updated_at')
23
+    readonly_fields_exclude = ('tracking_number', )
24
+
25
+
26
+admin.site.register(AnchorInfo, AnchorInfoAdmin)
27
+admin.site.register(RoomInfo, RoomInfoAdmin)
28
+admin.site.register(liveGoodsInfo, liveGoodsInfoAdmin)
29
+admin.site.register(RoomGoodsInfo, RoomGoodsInfoAdmin)
30
+admin.site.register(RoomOrderInfo, RoomOrderInfoAdmin)

+ 5 - 0
live/apps.py

@@ -0,0 +1,5 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class LiveConfig(AppConfig):
5
+    name = 'live'

+ 136 - 0
live/migrations/0001_initial.py

@@ -0,0 +1,136 @@
1
+# Generated by Django 2.2.12 on 2020-05-15 14:51
2
+
3
+from django.db import migrations, models
4
+import django_models_ext.fileext
5
+import jsonfield.fields
6
+import shortuuidfield.fields
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    initial = True
12
+
13
+    dependencies = [
14
+    ]
15
+
16
+    operations = [
17
+        migrations.CreateModel(
18
+            name='AnchorInfo',
19
+            fields=[
20
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
22
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
23
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
24
+                ('anchor_id', shortuuidfield.fields.ShortUUIDField(blank=True, editable=False, help_text='主播唯一标识', max_length=22, unique=True)),
25
+                ('unionid', models.CharField(blank=True, help_text='微信 Unionid', max_length=32, unique=True, verbose_name='unionid')),
26
+                ('openid', models.CharField(blank=True, help_text='微信 Openid', max_length=32, unique=True, verbose_name='openid')),
27
+                ('anchor_name', models.CharField(blank=True, db_index=True, help_text='主播姓名', max_length=255, verbose_name='anchor_name')),
28
+                ('anchor_avatar', models.CharField(blank=True, db_index=True, help_text='主播头像', max_length=255, verbose_name='anchor_avatar')),
29
+            ],
30
+            options={
31
+                'verbose_name': '主播信息',
32
+                'verbose_name_plural': '主播信息',
33
+            },
34
+        ),
35
+        migrations.CreateModel(
36
+            name='liveGoodsInfo',
37
+            fields=[
38
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
39
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
40
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
41
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
42
+                ('goods_id', shortuuidfield.fields.ShortUUIDField(blank=True, editable=False, help_text='商品ID', max_length=22, unique=True)),
43
+                ('wx_goods_id', models.CharField(blank=True, db_index=True, help_text='微信商品ID', max_length=255, verbose_name='wx_goods_id')),
44
+                ('audit_id', models.CharField(blank=True, db_index=True, help_text='审核单ID', max_length=255, verbose_name='anchor_name')),
45
+                ('audit_status', models.IntegerField(blank=True, db_index=True, default=0, help_text='审核状态', verbose_name='audit_status')),
46
+                ('goods_img', models.ImageField(help_text='商品图', upload_to=django_models_ext.fileext.upload_path, verbose_name='goods_img')),
47
+                ('coverImgUrl', models.CharField(blank=True, db_index=True, help_text='微信mediaID', max_length=255, verbose_name='anchor_name')),
48
+                ('name', models.CharField(blank=True, db_index=True, help_text='商品', max_length=34, verbose_name='name')),
49
+                ('price_type', models.IntegerField(db_index=True, help_text='价格类型', verbose_name='price_type')),
50
+                ('price', models.IntegerField(help_text='price', verbose_name='price')),
51
+                ('price2', models.IntegerField(help_text='price2', verbose_name='price2')),
52
+                ('url', models.CharField(blank=True, db_index=True, help_text='小程序商品路径', max_length=255, verbose_name='url')),
53
+            ],
54
+            options={
55
+                'verbose_name': '直播商品库',
56
+                'verbose_name_plural': '直播商品库',
57
+            },
58
+        ),
59
+        migrations.CreateModel(
60
+            name='RoomGoodsInfo',
61
+            fields=[
62
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
64
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
65
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
66
+                ('room_id', models.CharField(db_index=True, help_text='房间唯一标识', max_length=32, unique=True, verbose_name='room_id')),
67
+                ('anchor_id', models.CharField(blank=True, help_text='主播唯一标识', max_length=32, unique=True, verbose_name='anchor_id')),
68
+                ('goods_id', models.CharField(blank=True, help_text='商品ID', max_length=32, unique=True, verbose_name='goods_id')),
69
+                ('inventory', models.IntegerField(default=0, help_text='直播间库存数量', verbose_name='inventory')),
70
+                ('sale_infos', jsonfield.fields.JSONField(blank=True, default=[], help_text='直播间商品销售信息', verbose_name='sale_infos')),
71
+            ],
72
+            options={
73
+                'verbose_name': '直播间商品信息',
74
+                'verbose_name_plural': '直播间商品信息',
75
+            },
76
+        ),
77
+        migrations.CreateModel(
78
+            name='RoomInfo',
79
+            fields=[
80
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
81
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
82
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
83
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
84
+                ('room_id', models.CharField(db_index=True, help_text='房间唯一标识', max_length=32, unique=True, verbose_name='anchor_id')),
85
+                ('name', models.CharField(blank=True, db_index=True, help_text='直播房间名', max_length=255, verbose_name='name')),
86
+                ('live_status', models.IntegerField(db_index=True, help_text='直播状态', verbose_name='live_status')),
87
+                ('start_time', models.IntegerField(db_index=True, help_text='直播计划开始时间', verbose_name='start_time')),
88
+                ('end_time', models.IntegerField(db_index=True, help_text='直播计划结束时间', verbose_name='end_time')),
89
+                ('cover_img', models.ImageField(help_text='直播间背景墙', upload_to=django_models_ext.fileext.upload_path, verbose_name='cover_img')),
90
+                ('anchor_name', models.CharField(blank=True, db_index=True, help_text='主播名称', max_length=255, verbose_name='anchor_name')),
91
+                ('anchor_img', models.ImageField(help_text='分享卡片图片', upload_to=django_models_ext.fileext.upload_path, verbose_name='cover_img')),
92
+                ('anchor_id', models.CharField(help_text='主播唯一标识', max_length=32, unique=True, verbose_name='anchor_id')),
93
+                ('goods', jsonfield.fields.JSONField(blank=True, default=[], help_text='商品销售信息', verbose_name='goods')),
94
+            ],
95
+            options={
96
+                'verbose_name': '直播间信息',
97
+                'verbose_name_plural': '直播间信息',
98
+            },
99
+        ),
100
+        migrations.CreateModel(
101
+            name='RoomOrderInfo',
102
+            fields=[
103
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
104
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
105
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
106
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
107
+                ('order_id', shortuuidfield.fields.ShortUUIDField(blank=True, editable=False, help_text='商品ID', max_length=22, unique=True)),
108
+                ('user_id', models.CharField(db_index=True, help_text='用户唯一标识', max_length=32, unique=True, verbose_name='user_id')),
109
+                ('room_id', models.CharField(db_index=True, help_text='房间唯一标识', max_length=32, unique=True, verbose_name='room_id')),
110
+                ('anchor_id', models.CharField(blank=True, help_text='主播唯一标识', max_length=32, unique=True, verbose_name='anchor_id')),
111
+                ('goods_id', models.CharField(blank=True, help_text='商品ID', max_length=32, unique=True, verbose_name='goods_id')),
112
+                ('share_openid', models.CharField(blank=True, help_text='转发用户 openid', max_length=32, unique=True, verbose_name='share_openid')),
113
+                ('amount', models.IntegerField(default=0, help_text='数量', verbose_name='amount')),
114
+                ('name', models.CharField(blank=True, help_text='姓名', max_length=255, null=True, verbose_name='name')),
115
+                ('phone', models.CharField(blank=True, help_text='电话', max_length=255, null=True, verbose_name='phone')),
116
+                ('address', models.CharField(blank=True, help_text='地址', max_length=255, null=True, verbose_name='address')),
117
+                ('tracking_number', models.CharField(blank=True, help_text='快递单号', max_length=255, null=True, verbose_name='tracking_number')),
118
+                ('has_send_template_message', models.BooleanField(db_index=True, default=True, help_text='是否已发送模版消息', verbose_name='has_send_template_message')),
119
+                ('prepay_id', models.CharField(blank=True, help_text='预支付交易会话标识', max_length=64, null=True, verbose_name='prepay_id')),
120
+                ('transaction_id', models.CharField(blank=True, help_text='交易单号', max_length=32, null=True, verbose_name='transaction_id')),
121
+                ('body', models.CharField(blank=True, help_text='商品描述', max_length=255, null=True, verbose_name='body')),
122
+                ('total_fee', models.IntegerField(default=0, help_text='总金额', verbose_name='total_fee')),
123
+                ('trade_type', models.CharField(blank=True, help_text='支付方式', max_length=255, null=True, verbose_name='trade_type')),
124
+                ('pay_status', models.IntegerField(choices=[(0, '待支付'), (1, '已支付'), (2, '已失败')], db_index=True, default=0, help_text='支付状态', verbose_name='pay_status')),
125
+                ('paid_at', models.DateTimeField(blank=True, help_text='支付时间', null=True, verbose_name='paid_at')),
126
+                ('reback_status', models.BooleanField(db_index=True, default=False, help_text='退款状态', verbose_name='reback_status')),
127
+                ('reback_at', models.DateTimeField(blank=True, help_text='退款时间', null=True, verbose_name='reback_at')),
128
+                ('unifiedorder_result', models.TextField(blank=True, help_text='统一下单结果', null=True, verbose_name='unifiedorder_result')),
129
+                ('notify_msg', models.TextField(blank=True, help_text='回调信息', null=True, verbose_name='notify_msg')),
130
+            ],
131
+            options={
132
+                'verbose_name': '直播间订单',
133
+                'verbose_name_plural': '直播间订单',
134
+            },
135
+        ),
136
+    ]

+ 24 - 0
live/migrations/0002_auto_20200515_2321.py

@@ -0,0 +1,24 @@
1
+# Generated by Django 2.2.12 on 2020-05-15 15:21
2
+
3
+from django.db import migrations, models
4
+import shortuuidfield.fields
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('live', '0001_initial'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AlterField(
15
+            model_name='roomorderinfo',
16
+            name='goods_id',
17
+            field=models.CharField(blank=True, help_text='商品唯一标识', max_length=32, unique=True, verbose_name='goods_id'),
18
+        ),
19
+        migrations.AlterField(
20
+            model_name='roomorderinfo',
21
+            name='order_id',
22
+            field=shortuuidfield.fields.ShortUUIDField(blank=True, editable=False, help_text='订单唯一标识', max_length=22, unique=True),
23
+        ),
24
+    ]

+ 53 - 0
live/migrations/0003_auto_20200515_2323.py

@@ -0,0 +1,53 @@
1
+# Generated by Django 2.2.12 on 2020-05-15 15:23
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('live', '0002_auto_20200515_2321'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='roomgoodsinfo',
15
+            name='anchor_id',
16
+            field=models.CharField(blank=True, help_text='主播唯一标识', max_length=32, verbose_name='anchor_id'),
17
+        ),
18
+        migrations.AlterField(
19
+            model_name='roomgoodsinfo',
20
+            name='goods_id',
21
+            field=models.CharField(blank=True, help_text='商品ID', max_length=32, verbose_name='goods_id'),
22
+        ),
23
+        migrations.AlterField(
24
+            model_name='roominfo',
25
+            name='anchor_id',
26
+            field=models.CharField(help_text='主播唯一标识', max_length=32, verbose_name='anchor_id'),
27
+        ),
28
+        migrations.AlterField(
29
+            model_name='roomorderinfo',
30
+            name='anchor_id',
31
+            field=models.CharField(blank=True, help_text='主播唯一标识', max_length=32, verbose_name='anchor_id'),
32
+        ),
33
+        migrations.AlterField(
34
+            model_name='roomorderinfo',
35
+            name='goods_id',
36
+            field=models.CharField(blank=True, help_text='商品唯一标识', max_length=32, verbose_name='goods_id'),
37
+        ),
38
+        migrations.AlterField(
39
+            model_name='roomorderinfo',
40
+            name='room_id',
41
+            field=models.CharField(db_index=True, help_text='房间唯一标识', max_length=32, verbose_name='room_id'),
42
+        ),
43
+        migrations.AlterField(
44
+            model_name='roomorderinfo',
45
+            name='share_openid',
46
+            field=models.CharField(blank=True, help_text='转发用户 openid', max_length=32, verbose_name='share_openid'),
47
+        ),
48
+        migrations.AlterField(
49
+            model_name='roomorderinfo',
50
+            name='user_id',
51
+            field=models.CharField(db_index=True, help_text='用户唯一标识', max_length=32, verbose_name='user_id'),
52
+        ),
53
+    ]

+ 35 - 0
live/migrations/0004_auto_20200518_2323.py

@@ -0,0 +1,35 @@
1
+# Generated by Django 2.2.12 on 2020-05-18 15:23
2
+
3
+from django.db import migrations, models
4
+import django_models_ext.fileext
5
+import simditor.fields
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('live', '0003_auto_20200515_2323'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.RemoveField(
16
+            model_name='roominfo',
17
+            name='anchor_img',
18
+        ),
19
+        migrations.AddField(
20
+            model_name='anchorinfo',
21
+            name='anchor_intro',
22
+            field=simditor.fields.RichTextField(blank=True, help_text='主播介绍', verbose_name='anchor_intro'),
23
+        ),
24
+        migrations.AddField(
25
+            model_name='roominfo',
26
+            name='share_img',
27
+            field=models.ImageField(default='dd', help_text='分享卡片图片', upload_to=django_models_ext.fileext.upload_path, verbose_name='share_img'),
28
+            preserve_default=False,
29
+        ),
30
+        migrations.AlterField(
31
+            model_name='anchorinfo',
32
+            name='anchor_avatar',
33
+            field=models.ImageField(help_text='主播头像', upload_to=django_models_ext.fileext.upload_path, verbose_name='anchor_avatar'),
34
+        ),
35
+    ]

+ 0 - 0
live/migrations/__init__.py


+ 217 - 0
live/models.py

@@ -0,0 +1,217 @@
1
+from django.db import models
2
+from django.utils.translation import ugettext_lazy as _
3
+from django_models_ext import BaseModelMixin, upload_file_path, upload_file_url, upload_path
4
+from jsonfield import JSONField
5
+from shortuuidfield import ShortUUIDField
6
+from TimeConvert import TimeConvert as tc
7
+
8
+from account.models import UserInfo
9
+from simditor.fields import RichTextField
10
+
11
+class AnchorInfo(BaseModelMixin):
12
+    anchor_id = ShortUUIDField(_('anchor_id'), max_length=32, blank=True, help_text='主播唯一标识', unique=True)
13
+    # 微信相关
14
+    unionid = models.CharField(_('unionid'), max_length=32, blank=True, help_text='微信 Unionid', unique=True)
15
+    openid = models.CharField(_('openid'), max_length=32, blank=True, help_text='微信 Openid', unique=True)
16
+    # 真实相关
17
+    anchor_name = models.CharField(_('anchor_name'), max_length=255, blank=True, help_text='主播姓名', db_index=True)
18
+    anchor_avatar = models.ImageField(_('anchor_avatar'), upload_to=upload_path, help_text='主播头像')
19
+    anchor_intro = RichTextField(_('anchor_intro'), blank=True, help_text='主播介绍')
20
+    
21
+    class Meta:
22
+        verbose_name = _('主播信息')
23
+        verbose_name_plural = _('主播信息')
24
+
25
+    def __unicode__(self):
26
+        return self.pk
27
+    
28
+    @property
29
+    def anchor_avatar_path(self):
30
+        return upload_file_path(self.anchor_avatar)
31
+
32
+    @property
33
+    def anchor_avatar_url(self):
34
+        return upload_file_url(self.anchor_avatar)
35
+
36
+
37
+class RoomInfo(BaseModelMixin):
38
+    room_id = models.CharField(_('anchor_id'), max_length=32, help_text='房间唯一标识', db_index=True, unique=True)
39
+    name = models.CharField(_('name'), max_length=255, blank=True, help_text='直播房间名', db_index=True)
40
+    live_status = models.IntegerField(_('live_status'), help_text='直播状态', db_index=True)
41
+    start_time = models.IntegerField(_('start_time'), help_text='直播计划开始时间', db_index=True)
42
+    end_time = models.IntegerField(_('end_time'), help_text='直播计划结束时间', db_index=True)
43
+    cover_img = models.ImageField(_('cover_img'), upload_to=upload_path, help_text='直播间背景墙')
44
+    anchor_name = models.CharField(_('anchor_name'), blank=True, max_length=255, help_text='主播名称', db_index=True)
45
+    share_img = models.ImageField(_('share_img'), upload_to=upload_path, help_text='分享卡片图片')
46
+
47
+    anchor_id = models.CharField(_('anchor_id'), max_length=32, help_text='主播唯一标识')
48
+    
49
+    # "goods": [          
50
+    #     {
51
+    #         "cover_img": "http://mmbiz.qpic.cn/mmbiz_png/FVribAGdErI2PmyST9ZM0JLbNM48I7TH2FlrwYOlnYqGaej8qKubG1EvK0QIkkwqvicrYTzVtjKmSZSeY5ianc3mw/0?wx_fmt=png",
52
+    #         "url": "pages/index/index.html",
53
+    #         "price": 1100,
54
+    #         "name": "fdgfgf"
55
+    #     }
56
+    # ],
57
+    goods = JSONField(_('goods'), blank=True, default=[], help_text='商品销售信息')
58
+
59
+    class Meta:
60
+        verbose_name = _('直播间信息')
61
+        verbose_name_plural = _('直播间信息')
62
+
63
+    def __unicode__(self):
64
+        return self.pk
65
+    
66
+    @property
67
+    def cover_img_path(self):
68
+        return upload_file_path(self.cover_img)
69
+
70
+    @property
71
+    def cover_img_url(self):
72
+        return upload_file_url(self.cover_img)
73
+    
74
+    @property
75
+    def share_img_path(self):
76
+        return upload_file_path(self.share_img)
77
+
78
+    @property
79
+    def share_img_url(self):
80
+        return upload_file_url(self.share_img)
81
+
82
+    @property
83
+    def anchorData(self):
84
+        return {
85
+            'room_id': self.room_id,
86
+            'name': self.name,
87
+            'live_status': self.live_status,
88
+            'cover_img': self.cover_img_url,
89
+            'share_img': self.share_img_url,
90
+        }
91
+       
92
+
93
+  
94
+class liveGoodsInfo(BaseModelMixin):
95
+    goods_id = ShortUUIDField(_('goods_id'), max_length=32, blank=True, help_text='商品ID', unique=True)
96
+
97
+    wx_goods_id = models.CharField(_('wx_goods_id'), max_length=255, blank=True, help_text='微信商品ID', db_index=True)
98
+    audit_id = models.CharField(_('anchor_name'), max_length=255, blank=True, help_text='审核单ID', db_index=True)
99
+    audit_status = models.IntegerField(_('audit_status'), default=0, blank=True, help_text='审核状态', db_index=True)
100
+
101
+    goods_img = models.ImageField(_('goods_img'), upload_to=upload_path, help_text='商品图')
102
+
103
+    # {
104
+    #   "goodsInfo": {
105
+    #   "coverImgUrl": "ZuYVNKk9sMP1X4m7FXdcDCKra251KDZTjS502UTV7gwalgLZXcrOhG6oNYX6c7AR",
106
+    #   "name":"TIT茶杯",
107
+    #   "priceType":1,
108
+    #   "price":"111",
109
+    #   "price2":"",
110
+    #   "url":"pages/index/index"
111
+    #   }
112
+    # }
113
+    coverImgUrl = models.CharField(_('anchor_name'), max_length=255, blank=True, help_text='微信mediaID', db_index=True)
114
+    name = models.CharField(_('name'), max_length=34, help_text='商品', blank=True, db_index=True)
115
+
116
+    # 价格类型,1:一口价,2:价格区间,3:显示折扣价;1:一口价,只需要传入price,price2不传;2:价格区间,price字段为左边界,price2字段为右边界,price和price2必传。3:折扣价,price字段为原价,price2字段为现价, price和price2必传
117
+    price_type = models.IntegerField(_('price_type'), help_text='价格类型', db_index=True)
118
+    price = models.IntegerField(_('price'), help_text='price')
119
+    price2 = models.IntegerField(_('price2'), help_text='price2')
120
+    url = models.CharField(_('url'), max_length=255, blank=True, help_text='小程序商品路径', db_index=True)
121
+
122
+    class Meta:
123
+        verbose_name = _('直播商品库')
124
+        verbose_name_plural = _('直播商品库')
125
+
126
+    def __unicode__(self):
127
+        return self.pk
128
+
129
+    @property
130
+    def goods_img_path(self):
131
+        return upload_file_path(self.goods_img)
132
+
133
+    @property
134
+    def goods_img_url(self):
135
+        return upload_file_url(self.goods_img)
136
+
137
+
138
+
139
+class RoomGoodsInfo(BaseModelMixin):
140
+    room_id = models.CharField(_('room_id'), max_length=32, help_text='房间唯一标识', db_index=True, unique=True)
141
+    anchor_id = models.CharField(_('anchor_id'), max_length=32, blank=True, help_text='主播唯一标识')
142
+    goods_id = models.CharField(_('goods_id'), max_length=32, blank=True, help_text='商品ID')
143
+    inventory = models.IntegerField(_('inventory'), default=0, help_text='直播间库存数量')
144
+    sale_infos = JSONField(_('sale_infos'), blank=True, default=[], help_text='直播间商品销售信息')
145
+
146
+    class Meta:
147
+        verbose_name = _('直播间商品信息')
148
+        verbose_name_plural = _('直播间商品信息')
149
+
150
+    def __unicode__(self):
151
+        return self.pk
152
+
153
+class RoomOrderInfo(BaseModelMixin):
154
+    """
155
+    # Trade State of Wechat Query
156
+    SUCCESS ——— 支付成功
157
+    REFUND ——— 转入退款
158
+    NOTPAY ——— 未支付
159
+    CLOSED ——— 已关闭
160
+    REVOKED ——— 已撤销(刷卡支付)
161
+    USERPAYING ——— 用户支付中
162
+    PAYERROR ——— 支付失败(其他原因,如银行返回失败)
163
+    """
164
+
165
+    WAITING_PAY = 0
166
+    PAID = 1
167
+    FAIL = 2
168
+    # DELETED = 9
169
+
170
+    PAY_STATUS = (
171
+        (WAITING_PAY, '待支付'),
172
+        (PAID, '已支付'),
173
+        (FAIL, '已失败'),
174
+        # (DELETED, '已删除'),
175
+    )
176
+
177
+    order_id = ShortUUIDField(_('order_id'), max_length=32, blank=True, help_text='订单唯一标识', unique=True)
178
+    user_id = models.CharField(_('user_id'), max_length=32, help_text='用户唯一标识', db_index=True)
179
+    room_id = models.CharField(_('room_id'), max_length=32, help_text='房间唯一标识', db_index=True)
180
+    anchor_id = models.CharField(_('anchor_id'), max_length=32, blank=True, help_text='主播唯一标识')
181
+    goods_id = models.CharField(_('goods_id'), max_length=32, blank=True, help_text='商品唯一标识')
182
+    share_openid = models.CharField(_('share_openid'), max_length=32, blank=True, help_text='转发用户 openid')
183
+
184
+    amount = models.IntegerField(_('amount'), default=0, help_text='数量')
185
+    
186
+    name = models.CharField(_('name'), max_length=255, blank=True, null=True, help_text='姓名')
187
+    phone = models.CharField(_('phone'), max_length=255, blank=True, null=True, help_text='电话')
188
+    address = models.CharField(_('address'), max_length=255, blank=True, null=True, help_text='地址')
189
+
190
+    tracking_number = models.CharField(_('tracking_number'), max_length=255, blank=True, null=True, help_text='快递单号')
191
+    has_send_template_message = models.BooleanField(_('has_send_template_message'), default=True, help_text='是否已发送模版消息', db_index=True)
192
+
193
+    prepay_id = models.CharField(_('prepay_id'), max_length=64, blank=True, null=True, help_text='预支付交易会话标识')
194
+    transaction_id = models.CharField(_('transaction_id'), max_length=32, blank=True, null=True, help_text='交易单号')
195
+
196
+    body = models.CharField(_('body'), max_length=255, blank=True, null=True, help_text='商品描述')
197
+    total_fee = models.IntegerField(_('total_fee'), default=0, help_text='总金额')
198
+
199
+    trade_type = models.CharField(_('trade_type'), max_length=255, blank=True, null=True, help_text='支付方式')
200
+
201
+    pay_status = models.IntegerField(_('pay_status'), choices=PAY_STATUS, default=WAITING_PAY, help_text='支付状态', db_index=True)
202
+    paid_at = models.DateTimeField(_('paid_at'), blank=True, null=True, help_text=_('支付时间'))
203
+
204
+    reback_status = models.BooleanField(_('reback_status'), default=False, help_text='退款状态', db_index=True)
205
+    reback_at = models.DateTimeField(_('reback_at'), blank=True, null=True, help_text=_('退款时间'))
206
+
207
+    # 微信统一下单
208
+    unifiedorder_result = models.TextField(_('unifiedorder_result'), blank=True, null=True, help_text=_('统一下单结果'))
209
+    # 微信支付回调
210
+    notify_msg = models.TextField(_('notify_msg'), blank=True, null=True, help_text='回调信息')
211
+
212
+    class Meta:
213
+        verbose_name = _('直播间订单')
214
+        verbose_name_plural = _('直播间订单')
215
+
216
+    def __unicode__(self):
217
+        return self.pk

+ 3 - 0
live/tests.py

@@ -0,0 +1,3 @@
1
+from django.test import TestCase
2
+
3
+# Create your tests here.

+ 248 - 0
live/views.py

@@ -0,0 +1,248 @@
1
+from django.shortcuts import render
2
+from django_query import get_query_value
3
+from django.db import transaction
4
+from django_response import response
5
+from django.shortcuts import HttpResponse
6
+from django_logit import logit
7
+from pywe_exception import WeChatPayException
8
+from pywe_pay import WeChatPay
9
+from pywe_pay_notify import check_pay_notify
10
+from pywe_response import WXPAY_NOTIFY_FAIL, WXPAY_NOTIFY_SUCCESS
11
+from TimeConvert import TimeConvert as tc
12
+
13
+from django.conf import settings
14
+
15
+from live.models import liveGoodsInfo, RoomGoodsInfo, RoomOrderInfo, RoomInfo, AnchorInfo
16
+from account.models import UserInfo
17
+
18
+from utils.error.errno_utils import OrderStatusCode, UserStatusCode
19
+
20
+WECHAT = settings.WECHAT
21
+
22
+def room_goods_detail(request):
23
+  goods_id = get_query_value(request, 'goods_id', '')
24
+  room_id = get_query_value(request, 'room_id', '')
25
+  
26
+  # 校验直播间和商品
27
+  try:
28
+    room_goods_info = RoomGoodsInfo.objects.get(goods_id=goods_id, room_id=room_id)
29
+  except:
30
+    return response()
31
+  
32
+  try:
33
+    goods_info = liveGoodsInfo.objects.get(goods_id=goods_id)
34
+  except:
35
+    return response()
36
+
37
+  return response(200, 'Get Room Goods Detail Success', '获取直播间商品成功', data={
38
+        'goods_info': {
39
+          'goods_img': goods_info.goods_img_url,
40
+          'name': goods_info.name,
41
+          'price_type': goods_info.price_type,
42
+          'price': goods_info.price,
43
+          'price2': goods_info.price2,
44
+          'inventory': room_goods_info.inventory,
45
+        },
46
+        'anchor_id': room_goods_info.anchor_id,
47
+      })
48
+
49
+WECHAT = settings.WECHAT
50
+
51
+def room_anchor_details(request):
52
+    room_id = request.POST.get('room_id', '')
53
+
54
+    try:
55
+        room = RoomInfo.objects.get(room_id=room_id)
56
+    except:
57
+        return response()
58
+    
59
+    try:
60
+        anchor = AnchorInfo.objects.get(anchor_id=room.anchor_id)
61
+    except:
62
+        return response()
63
+    
64
+    rooms = RoomInfo.objects.get(anchor_id=anchor_id)
65
+    rooms = [room.anchorData for room in rooms]
66
+    
67
+    return response(200, 'Get Room Anchor Details Success', '获取主播详情成功', data={
68
+        'anchor': {
69
+            'anchor_id': anchor.anchor_id,
70
+            'anchor_name': anchor.anchor_name,
71
+            'anchor_avatar': anchor.anchor_avatar_url,
72
+            'anchor_intro': anchor.anchor_intro
73
+        },
74
+        'rooms': rooms
75
+    })
76
+
77
+@logit
78
+@transaction.atomic
79
+def live_order_create(request):
80
+    """ 订单创建 """
81
+    user_id = request.POST.get('user_id', '')
82
+    room_id = request.POST.get('room_id', '')
83
+    anchor_id = request.POST.get('anchor_id', '')
84
+    goods_id = request.POST.get('goods_id', '')
85
+    share_openid = request.POST.get('share_openid', '')
86
+
87
+    name = request.POST.get('name', '')
88
+    phone = request.POST.get('phone', '')
89
+    address = request.POST.get('address', '')
90
+
91
+    amount = int(request.POST.get('amount', 0))
92
+    total_fee = int(request.POST.get('total_fee', 0))  # 总金额,单位分
93
+
94
+    body = request.POST.get('body', '尖货直播')  # 商品描述
95
+
96
+    # 用户校验
97
+    try:
98
+        user = UserInfo.objects.get(user_id=user_id, status=True)
99
+        user.consignee_name = name
100
+        user.consignee_phone = phone
101
+        user.consignee_address = address
102
+        user.save()
103
+    except UserInfo.DoesNotExist:
104
+        return response(UserStatusCode.USER_NOT_FOUND)
105
+
106
+    # 校验直播间和商品
107
+    try:
108
+        room_goods_info = RoomGoodsInfo.objects.get(goods_id=goods_id, room_id=room_id, anchor_id=anchor_id)
109
+    except:
110
+        return response(400001, 'Room Goods Not Found', description='直播间商品不存在')
111
+
112
+    # 金额校验
113
+    try:
114
+        goods_info = liveGoodsInfo.objects.get(goods_id=goods_id)
115
+        if amount * int(goods_info.price) != total_fee:
116
+            return response(404091, 'FEE Check Fail', description='金额校验失败')
117
+    except:
118
+        return response(404091, 'FEE Check Fail', description='金额校验失败')
119
+
120
+    # 消库存
121
+    room_goods_info.inventory -= 1
122
+    room_goods_info.save()
123
+
124
+    # JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里
125
+    trade_type = 'JSAPI'
126
+
127
+    # 根据 trade_type 获取 wechat 配置
128
+    wxcfg = WECHAT.get(trade_type, {})
129
+    # WeChatPay 初始化
130
+    wxpay = WeChatPay(wxcfg.get('appID'), wxcfg.get('apiKey'), wxcfg.get('mchID'))
131
+
132
+    # 生成订单
133
+    order = RoomOrderInfo.objects.create(
134
+        user_id=user_id,
135
+        room_id=room_id,
136
+        anchor_id=anchor_id,
137
+        goods_id=goods_id,
138
+        share_openid=share_openid,
139
+        amount=amount,
140
+        total_fee=total_fee,
141
+        trade_type=trade_type,
142
+        name=name,
143
+        phone=phone,
144
+        address=address,
145
+    )
146
+
147
+    try:
148
+        prepay_data = wxpay.order.create(
149
+            body=body,
150
+            notify_url=settings.API_DOMAIN + 'live/pay/notify_url',
151
+            out_trade_no=order.order_id,
152
+            total_fee=total_fee,
153
+            trade_type=trade_type,
154
+            openid=user.openid,  # 可选,用户在商户appid下的唯一标识。trade_type=JSAPI,此参数必传
155
+        )
156
+    except WeChatPayException as e:
157
+        order.unifiedorder_result = e.args
158
+        order.save()
159
+        return response(OrderStatusCode.UNIFIED_ORDER_FAIL)
160
+
161
+    prepay_id = prepay_data.get('prepay_id', '')
162
+    order.prepay_id = prepay_id
163
+    order.save()
164
+
165
+    wxpay_params = wxpay.jsapi.get_jsapi_params(prepay_id)
166
+    
167
+
168
+    return response(200, 'Order Create Success', '订单创建成功', {
169
+        'order_id': order.order_id,
170
+        'prepay_id': prepay_id,
171
+        'wxpay_params': wxpay_params,
172
+    })
173
+
174
+def live_order_cancel(request):
175
+    user_id = request.POST.get('user_id', '')
176
+    order_id = request.POST.get('order_id', '')
177
+    prepay_id = request.POST.get('prepay_id', '')
178
+
179
+    try:
180
+        order = RoomOrderInfo.objects.get(user_id=user_id, order_id=order_id, prepay_id=prepay_id)
181
+    except:
182
+        return response(400001, 'Order Not Found', description='直播间订单不存在')
183
+    
184
+    if order.pay_status == RoomOrderInfo.FAIL:
185
+        return response(200, 'Order Cancel Success', '订单取消成功')
186
+    
187
+    order.pay_status = RoomOrderInfo.FAIL
188
+    order.save()
189
+    
190
+    try:
191
+        goods_info = RoomGoodsInfo.objects.get(room_id=order.room_id, goods_id=order.goods_id, anchor_id=order.anchor_id)
192
+        goods_info.inventory += order.amount
193
+        goods_info.save()
194
+    except:
195
+        return response(400001, 'Room Goods Not Found', description='直播间商品不存在')
196
+    
197
+    return response(200, 'Order Cancel Success', '订单取消成功')
198
+
199
+def order_paid_success(order):
200
+    if order.pay_status == RoomOrderInfo.PAID:
201
+        return
202
+
203
+    order.pay_status = RoomOrderInfo.PAID
204
+    order.paid_at = tc.utc_datetime()
205
+    order.save()
206
+
207
+    try:
208
+        goods_info = RoomGoodsInfo.objects.get(room_id=order.room_id, goods_id=order.goods_id, anchor_id=order.anchor_id)
209
+        goods_info.sale_infos += [order.order_id]
210
+        goods_info.save()
211
+    except:
212
+        return
213
+
214
+def order_paid_fail(order):
215
+    if order.pay_status == RoomGoodsInfo.FAIL:
216
+        return
217
+
218
+    order.pay_status = RoomGoodsInfo.FAIL
219
+    order.save()
220
+
221
+    try:
222
+        goods_info = RoomGoodsInfo.objects.get(room_id=order.room_id, goods_id=order.goods_id, anchor_id=order.anchor_id)
223
+        goods_info.inventory += order.amount
224
+        goods_info.save()
225
+    except:
226
+        return
227
+
228
+@logit
229
+@transaction.atomic
230
+def notify_url_api(request):
231
+    """ 支付异步通知回调地址 """
232
+    notify_data, success = check_pay_notify(request.body, wx_configs=settings.WECHAT)
233
+    if not success:
234
+        return HttpResponse(WXPAY_NOTIFY_FAIL)
235
+
236
+    order = RoomOrderInfo.objects.select_for_update().get(order_id=notify_data.get('out_trade_no', ''), status=True)
237
+
238
+    order.notify_msg = request.body
239
+    order.transaction_id = notify_data.get('transaction_id', '')
240
+    order.save()
241
+
242
+    result_code = notify_data.get('result_code', '')
243
+    if result_code == 'SUCCESS':
244
+        live_views.order_paid_success(order)
245
+    else:
246
+        live_views.order_paid_fail(order)
247
+
248
+    return HttpResponse(WXPAY_NOTIFY_SUCCESS)

+ 16 - 10
pay/views.py

@@ -18,8 +18,11 @@ from account.models import UserInfo
18 18
 from goods.models import GoodsInfo, PackGoodsInfo, PackGoodsSaleInfo, PackInfo
19 19
 from kol.models import KOLInfo
20 20
 from pay.models import OrderInfo
21
+from live.models import RoomOrderInfo
21 22
 from utils.error.errno_utils import KOLStatusCode, OrderStatusCode, PackGoodsStatusCode, PackStatusCode, UserStatusCode
22 23
 
24
+from live import views as live_views
25
+
23 26
 
24 27
 WECHAT = settings.WECHAT
25 28
 
@@ -215,19 +218,22 @@ def wx_notify_url_api(request):
215 218
     if not success:
216 219
         return HttpResponse(WXPAY_NOTIFY_FAIL)
217 220
 
221
+    #尖货接龙订单
218 222
     try:
219 223
         order = OrderInfo.objects.select_for_update().get(order_id=notify_data.get('out_trade_no', ''), status=True)
220
-    except OrderInfo.DoesNotExist:
221
-        return HttpResponse(WXPAY_NOTIFY_FAIL)
224
+        order.notify_msg = request.body
225
+        order.transaction_id = notify_data.get('transaction_id', '')
226
+        order.save()
222 227
 
223
-    order.notify_msg = request.body
224
-    order.transaction_id = notify_data.get('transaction_id', '')
225
-    order.save()
228
+        result_code = notify_data.get('result_code', '')
229
+        if result_code == 'SUCCESS':
230
+            order_paid_success(order)
231
+        else:
232
+            order_paid_fail(order)
233
+        
234
+        return HttpResponse(WXPAY_NOTIFY_SUCCESS)
235
+    except:
236
+        return HttpResponse(WXPAY_NOTIFY_FAIL)
226 237
 
227
-    result_code = notify_data.get('result_code', '')
228
-    if result_code == 'SUCCESS':
229
-        order_paid_success(order)
230
-    else:
231
-        order_paid_fail(order)
232 238
 
233 239
     return HttpResponse(WXPAY_NOTIFY_SUCCESS)